#pragma once
#include <memory>
#include <unordered_map>
#include <functional>

#include <unistd.h>
#include <sys/select.h>
#include "Socket.hpp"
#include "Server.hpp"
#include "ClientInfo.hpp"
#include "Log.hpp"
#include "Error.hpp"
#include "Protocol.hpp"


/*
    多路转接之 select
*/
//

// 动态增长来管理 用户关心的文件描述符
// 在达到阈值之前2倍增长，超过阈值线性增长
const int fd_sz = sizeof(fd_set) * 8;
const int fd_init = 2;
const int fd_threshold = 20;
const int fd_increase = 4;
const int fd_default = -1;

enum  SEL_EVENT
{
    EV_DEFAULT = 0,   // 不关心
    READ_EV = 1,  // 读事件
    WRITE_EV = 2, // 写事件
    EXCEPT_EV = 4 // 异常事件
};


// 所关心的文件描述符的事件，以及其对应的ip,port
typedef struct Events
{
    Events(int fd = fd_default , SEL_EVENT event = EV_DEFAULT)
    :_fd(fd)
    ,_event(event)
    {}
    
    int _fd;
    int _event;
    Client _cli;
    std::string inbuf; //输入缓冲区，接收请求的缓冲区
    std::string outbuf; // 输出缓冲区，存放响应的缓冲区

}type_t;


// 先写出echo服务器
// 再添加其他复杂业务

// 回调函数，处理业务
using func_t = std::function<Response(const Request&)>;


class SelectServer : public Server
{
public:
    using ptr = std::unique_ptr<SelectServer>;
    SelectServer()
    {
        InitServer();
        _events = new type_t[fd_init];
        _events_sz = fd_init;
    }

    SelectServer(func_t cb)
    :_cb(cb)
    {
        _events = new type_t[fd_init];
        _events_sz = fd_init;
    }

    // 此时SelectServer再进行accept时，不会被阻塞了。
    // 因为select告知我listenfd，有事件就绪了，此时
    // 我进行读取即可。
           
    bool AddSpace()
    {
        int newsz = _events_sz < fd_threshold ? _events_sz * 2 : _events_sz + fd_increase;
        if(newsz > fd_sz)
            return false;
        type_t* tmp = new type_t[newsz];
        std::copy(_events, _events + _events_sz, tmp);
        delete [] _events;
        // 还要更新大小!!
        _events_sz = newsz;
        _events = tmp;
        return true;
    }

    void AcceptHandler()
    {
        Client cli;
        int sock = _listensock.Accept(&cli._ip,&cli._port);
        // 添加进用户关心的文件描述符集合中
        int i = 0;
        // 找到_events中第一个没有被使用的位置
        while( i < _events_sz && _events[i]._fd != fd_default) i++;
        
        // 是否需要进行扩容
        if(i == _events_sz)
        {
            if(!AddSpace())
            {
                LOG(INFO,"select 关心的文件描述符已达上限");
                return;
            }
            cout << "扩容成功" << endl;
        }
        // 到此处是 [i].fd == fd_default
        _events[i]._fd = sock;
        _events[i]._event |= READ_EV;
        _events[i]._cli = cli;
    }
    void IsEnableWriteEvent(type_t& event,bool flag)
    {
        if(flag == true)
            event._event |= WRITE_EV;
        else
            event._event &= ~WRITE_EV;
                
    }
    // 1.先测试echo 服务器
    // 2.再进行CacularServer
    void ReadHandler(type_t& event)
    {
        // 此时进行读，是不会被阻塞的，因为select通知我读事件已经就绪了
        // recv(int sockfd, void *buf, size_t len, int flags);
        char buf[1024];
        // -1：为\0预留空间
        int n = recv(event._fd,buf,sizeof(buf) - 1,0);
        if(n == 0)
        {
            LOG(INFO,"对端[%s-%d] 关闭",event._cli._ip.c_str(),event._cli._port);
            memset(&event,0,sizeof(event));
            event._fd = fd_default;
        }
        else if(n > 0)
        {
            buf[n] = 0;
            // cout << "buf" << buf << endl;
            // 将接收过来的数据存放到输入缓冲区
            event.inbuf += buf;
            // cout << "GetMsg before:: inbuf" << inbuf << endl;

            // 边读取边解析
            std::string msg; // 报文
            if(Protocol::GetMsg(event.inbuf,&msg) == false)
            {
                //表示缓冲区内部没有一份完整的报文
                return;
            }
            // cout << "GetMsg after:: inbuf" << inbuf << endl;
            // cout << "msg  " << msg << endl;

            // 获取到完整的报文
            // 获取有效载荷
            std::string body = Protocol::DelHeader(msg);
            // 反序列化
            Request req;
            req.DisSerialize(body);
            // 得到请求，处理请求,得到响应
            Response resp = _cb(req);
            // using callback = std::function<Response(const Request&)>;

            // 序列化
            std::string resp_str;
            resp.Serialize(&resp_str);
            // 添加报头
            Protocol::AddHeader(resp_str);
            // 在多路转接中写事件一般不关心
            // 因为大部分情况下，写事件是就绪的，对方的是有足够的缓冲区的
            // 如果写事件就绪，多路转接每次都会提醒我写事件就绪，白白浪费资源
            n = send(event._fd,resp_str.c_str(),resp_str.size(),0);
            if(n == -1)
            {
                // 发送失败，我再打开对写事件的关心，并把未发送成功的数据加入到输出缓冲区中
                IsEnableWriteEvent(event,true);
                event.outbuf += resp_str;
                LOG(ERROR,"send fail %d-%s",errno,strerror(errno));
            }
            else
            {
                LOG(INFO,"发送成功 n = %d",n);
            }
        }
        else
        {
            LOG(INFO,"recv failed %d-%s",errno,strerror(errno));
        }
        cout << "-----------------------" << endl;

    }
    void WriteHandler(type_t& event)
    {
        // outbuf中存储的都是，处理好的响应报文
        // 我们直接全部发送
        int n = send(event._fd,event.outbuf.c_str(),event.outbuf.size(),0);
        if(n > 0)
        {
            // 如果outbuf中的数据已经全部发送成功了
            // 则关闭对写事件的关心
            if(n == event.outbuf.size())
                IsEnableWriteEvent(event,false);
            //发送成功，将数据从outbuf中删除
            event.outbuf.erase(0,n);

        }
    }

    void EvenHandler(fd_set *readfds, fd_set *writefds,fd_set *exceptfds)
    {
        for(int i = 0; i < _events_sz; i++)
        {
            int fd = _events[i]._fd;
            if(FD_ISSET(fd,readfds))
            {
                if(fd == _listenfd)
                {
                    LOG(INFO,"新连接到来");
                    AcceptHandler();
                }
                else
                {
                    LOG(INFO,"%d-读事件就绪",_events[i]._fd);
                    ReadHandler(_events[i]);
                }
            }
            else if(FD_ISSET(fd,writefds))
            {
                LOG(INFO,"%d-写事件就绪",i);
                WriteHandler(_events[i]);
            }
        }
    }

    void ShowFds()
    {
        cout << "select 关心的文件描述符: ";
        for(int i = 0; i < _events_sz;i++)
        {
            if(_events[i]._event != EV_DEFAULT)
                cout << _events[i]._fd << " ";
        }
        cout << endl;
    }

    // 事件派发器
    // 让select 关心用户想要关心的文件描述符的读事件，写事件，异常事件
    // 如果该文件描述符上的某个事件就绪后，进行事件派发让它处理对应的事件
    void Dispatcher()
    {
        LOG(INFO,"select server is running...");
        // 1.让select关心监听套接字
        // 在网络编程中，对于listensock 建立连接时，是算作读事件！
        _listenfd = _listensock.Fd();
        _events[0]._event = READ_EV;
        _events[0]._fd = _listenfd;
        while(true)
        {

            // 因为select的参数是输入输出型参数，意味着当os告知用户时是通过 readfds,writefds,exceptfds进行通知的
            // 也就是说，曾经我们让select关心的文件描述符已经不见了。所以我们需要每次都设置关心的事件
            fd_set readfds;
            fd_set writefds;
            FD_ZERO(&readfds);
            FD_ZERO(&writefds);
            int fd_max = 0;
            for(int i = 0; i < _events_sz;i++)
            {
                int fd = _events[i]._fd;
                if(_events[i]._event == READ_EV)
                    FD_SET(fd,&readfds);
                else if(_events[i]._event == WRITE_EV)
                    FD_SET(fd,&writefds);
                if(_events[i]._fd + 1 > fd_max)
                    fd_max = _events[i]._fd + 1;
            }
            // LOG(INFO,"max_fd = %d",fd_max);
            // nullptr 阻塞的方式等待
            // 2s内阻塞，超过2s，非阻塞
            struct timeval timeout;
            timeout.tv_sec = 2;
            timeout.tv_usec = 0;
            // nfds是最大的文件描述符
            // int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
            int n = select(fd_max,&readfds,&writefds,nullptr,&timeout);
            if(n == -1)
            {
                LOG(INFO,"select 失败");
                LOG(ERROR,"%d - %s", errno, strerror(errno));
            }
            else if(n > 0)
            {
                // LOG(INFO,"有事件就绪了");
                EvenHandler(&readfds,&writefds,nullptr);
            }
            else
            {
                // LOG(INFO,"没有事件就绪");
                // ShowFds();
            }
            sleep(1);
        }
    }

    virtual ~SelectServer()
    {
        delete[] _events;
    }

private:
    // 这样管理太分散了
    // int *_fds;
    // int fd_max;
    // int _fds_sz;
    int _events_sz;
    int _listenfd;
    // std::unordered_map<int,Client> _clients;
    type_t* _events;
    func_t _cb;
};